View Javadoc
1   package org.apache.maven.surefire.testng;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.booter.ProviderParameterNames;
23  import org.apache.maven.surefire.cli.CommandLineOption;
24  import org.apache.maven.surefire.report.RunListener;
25  import org.apache.maven.surefire.testng.conf.Configurator;
26  import org.apache.maven.surefire.testng.utils.FailFastEventsSingleton;
27  import org.apache.maven.surefire.testng.utils.FailFastListener;
28  import org.apache.maven.surefire.testng.utils.Stoppable;
29  import org.apache.maven.surefire.testset.TestListResolver;
30  import org.apache.maven.surefire.testset.TestSetFailedException;
31  import org.apache.maven.surefire.util.internal.StringUtils;
32  import org.testng.TestNG;
33  import org.testng.annotations.Test;
34  import org.testng.xml.XmlClass;
35  import org.testng.xml.XmlMethodSelector;
36  import org.testng.xml.XmlSuite;
37  import org.testng.xml.XmlTest;
38  
39  import java.io.File;
40  import java.lang.annotation.Annotation;
41  import java.lang.reflect.Constructor;
42  import java.lang.reflect.InvocationTargetException;
43  import java.lang.reflect.Method;
44  import java.util.ArrayList;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.concurrent.atomic.AtomicInteger;
49  
50  import static org.apache.maven.surefire.util.ReflectionUtils.instantiate;
51  import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;
52  import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero;
53  
54  /**
55   * Contains utility methods for executing TestNG.
56   *
57   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
58   * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
59   */
60  final class TestNGExecutor
61  {
62      /** The default name for a suite launched from the maven surefire plugin */
63      private static final String DEFAULT_SUREFIRE_SUITE_NAME = "Surefire suite";
64  
65      /** The default name for a test launched from the maven surefire plugin */
66      private static final String DEFAULT_SUREFIRE_TEST_NAME = "Surefire test";
67  
68      private static final boolean HAS_TEST_ANNOTATION_ON_CLASSPATH =
69              tryLoadClass( TestNGExecutor.class.getClassLoader(), "org.testng.annotations.Test" ) != null;
70  
71      private TestNGExecutor()
72      {
73          throw new IllegalStateException( "not instantiable constructor" );
74      }
75  
76      static void run( Iterable<Class<?>> testClasses, String testSourceDirectory,
77                              Map<String, String> options, // string,string because TestNGMapConfigurator#configure()
78                              RunListener reportManager, File reportsDirectory,
79                              TestListResolver methodFilter, List<CommandLineOption> mainCliOptions,
80                              int skipAfterFailureCount )
81          throws TestSetFailedException
82      {
83          TestNG testng = new TestNG( true );
84  
85          Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );
86  
87          if ( isCliDebugOrShowErrors( mainCliOptions ) )
88          {
89              System.out.println( "Configuring TestNG with: " + configurator.getClass().getSimpleName() );
90          }
91  
92          XmlMethodSelector groupMatchingSelector = createGroupMatchingSelector( options );
93          XmlMethodSelector methodNameFilteringSelector = createMethodNameFilteringSelector( methodFilter );
94  
95          Map<String, SuiteAndNamedTests> suitesNames = new HashMap<String, SuiteAndNamedTests>();
96  
97          List<XmlSuite> xmlSuites = new ArrayList<XmlSuite>();
98          for ( Class<?> testClass : testClasses )
99          {
100             TestMetadata metadata = findTestMetadata( testClass );
101 
102             SuiteAndNamedTests suiteAndNamedTests = suitesNames.get( metadata.suiteName );
103             if ( suiteAndNamedTests == null )
104             {
105                 suiteAndNamedTests = new SuiteAndNamedTests();
106                 suiteAndNamedTests.xmlSuite.setName( metadata.suiteName );
107                 configurator.configure( suiteAndNamedTests.xmlSuite, options );
108                 xmlSuites.add( suiteAndNamedTests.xmlSuite );
109 
110                 suitesNames.put( metadata.suiteName, suiteAndNamedTests );
111             }
112 
113             XmlTest xmlTest = suiteAndNamedTests.testNameToTest.get( metadata.testName );
114             if ( xmlTest == null )
115             {
116                 xmlTest = new XmlTest( suiteAndNamedTests.xmlSuite );
117                 xmlTest.setName( metadata.testName );
118                 addSelector( xmlTest, groupMatchingSelector );
119                 addSelector( xmlTest, methodNameFilteringSelector );
120                 xmlTest.setXmlClasses( new ArrayList<XmlClass>() );
121 
122                 suiteAndNamedTests.testNameToTest.put( metadata.testName, xmlTest );
123             }
124 
125             xmlTest.getXmlClasses().add( new XmlClass( testClass.getName() ) );
126         }
127 
128         testng.setXmlSuites( xmlSuites );
129         configurator.configure( testng, options );
130         postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,
131                        extractVerboseLevel( options ) );
132         testng.run();
133     }
134 
135     private static boolean isCliDebugOrShowErrors( List<CommandLineOption> mainCliOptions )
136     {
137         return mainCliOptions.contains( CommandLineOption.LOGGING_LEVEL_DEBUG )
138             || mainCliOptions.contains( CommandLineOption.SHOW_ERRORS );
139     }
140 
141     private static TestMetadata findTestMetadata( Class<?> testClass )
142     {
143         TestMetadata result = new TestMetadata();
144         if ( HAS_TEST_ANNOTATION_ON_CLASSPATH )
145         {
146             Test testAnnotation = findAnnotation( testClass, Test.class );
147             if ( null != testAnnotation )
148             {
149                 if ( !StringUtils.isBlank( testAnnotation.suiteName() ) )
150                 {
151                     result.suiteName = testAnnotation.suiteName();
152                 }
153 
154                 if ( !StringUtils.isBlank( testAnnotation.testName() ) )
155                 {
156                     result.testName = testAnnotation.testName();
157                 }
158             }
159         }
160         return result;
161     }
162 
163     private static <T extends Annotation> T findAnnotation( Class<?> clazz, Class<T> annotationType )
164     {
165         if ( clazz == null )
166         {
167             return null;
168         }
169 
170         T result = clazz.getAnnotation( annotationType );
171         if ( result != null )
172         {
173             return result;
174         }
175 
176         return findAnnotation( clazz.getSuperclass(), annotationType );
177     }
178 
179     private static class TestMetadata
180     {
181         private String testName = DEFAULT_SUREFIRE_TEST_NAME;
182 
183         private String suiteName = DEFAULT_SUREFIRE_SUITE_NAME;
184     }
185 
186     private static class SuiteAndNamedTests
187     {
188         private XmlSuite xmlSuite = new XmlSuite();
189 
190         private Map<String, XmlTest> testNameToTest = new HashMap<String, XmlTest>();
191     }
192 
193     private static void addSelector( XmlTest xmlTest, XmlMethodSelector selector )
194     {
195         if ( selector != null )
196         {
197             xmlTest.getMethodSelectors().add( selector );
198         }
199     }
200 
201     @SuppressWarnings( "checkstyle:magicnumber" )
202     private static XmlMethodSelector createMethodNameFilteringSelector( TestListResolver methodFilter )
203         throws TestSetFailedException
204     {
205         if ( methodFilter != null && !methodFilter.isEmpty() )
206         {
207             // the class is available in the testClassPath
208             String clazzName = "org.apache.maven.surefire.testng.utils.MethodSelector";
209             try
210             {
211                 Class<?> clazz = Class.forName( clazzName );
212                 Method method = clazz.getMethod( "setTestListResolver", TestListResolver.class );
213                 method.invoke( null, methodFilter );
214             }
215             catch ( Exception e )
216             {
217                 throw new TestSetFailedException( e.getMessage(), e );
218             }
219 
220             XmlMethodSelector xms = new XmlMethodSelector();
221 
222             xms.setName( clazzName );
223             // looks to need a high value
224             xms.setPriority( 10000 );
225 
226             return xms;
227         }
228         else
229         {
230             return null;
231         }
232     }
233 
234     @SuppressWarnings( "checkstyle:magicnumber" )
235     private static XmlMethodSelector createGroupMatchingSelector( Map<String, String> options )
236         throws TestSetFailedException
237     {
238         final String groups = options.get( ProviderParameterNames.TESTNG_GROUPS_PROP );
239         final String excludedGroups = options.get( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );
240 
241         if ( groups == null && excludedGroups == null )
242         {
243             return null;
244         }
245 
246         // the class is available in the testClassPath
247         final String clazzName = "org.apache.maven.surefire.testng.utils.GroupMatcherMethodSelector";
248         try
249         {
250             Class<?> clazz = Class.forName( clazzName );
251 
252             // HORRIBLE hack, but TNG doesn't allow us to setup a method selector instance directly.
253             Method method = clazz.getMethod( "setGroups", String.class, String.class );
254             method.invoke( null, groups, excludedGroups );
255         }
256         catch ( Exception e )
257         {
258             throw new TestSetFailedException( e.getMessage(), e );
259         }
260 
261         XmlMethodSelector xms = new XmlMethodSelector();
262 
263         xms.setName( clazzName );
264         // looks to need a high value
265         xms.setPriority( 9999 );
266 
267         return xms;
268     }
269 
270     static void run( List<String> suiteFiles, String testSourceDirectory,
271                             Map<String, String> options, // string,string because TestNGMapConfigurator#configure()
272                             RunListener reportManager, File reportsDirectory, int skipAfterFailureCount )
273         throws TestSetFailedException
274     {
275         TestNG testng = new TestNG( true );
276         Configurator configurator = getConfigurator( options.get( "testng.configurator" ) );
277         configurator.configure( testng, options );
278         postConfigure( testng, testSourceDirectory, reportManager, reportsDirectory, skipAfterFailureCount,
279                        extractVerboseLevel( options ) );
280         testng.setTestSuites( suiteFiles );
281         testng.run();
282     }
283 
284     private static Configurator getConfigurator( String className )
285     {
286         try
287         {
288             return (Configurator) Class.forName( className ).newInstance();
289         }
290         catch ( InstantiationException e )
291         {
292             throw new RuntimeException( e );
293         }
294         catch ( IllegalAccessException e )
295         {
296             throw new RuntimeException( e );
297         }
298         catch ( ClassNotFoundException e )
299         {
300             throw new RuntimeException( e );
301         }
302     }
303 
304     private static void postConfigure( TestNG testNG, String sourcePath, final RunListener reportManager,
305                                        File reportsDirectory, int skipAfterFailureCount, int verboseLevel )
306     {
307         // 0 (default): turn off all TestNG output
308         testNG.setVerbose( verboseLevel );
309 
310         TestNGReporter reporter = createTestNGReporter( reportManager );
311         testNG.addListener( (Object) reporter );
312 
313         if ( skipAfterFailureCount > 0 )
314         {
315             ClassLoader cl = Thread.currentThread().getContextClassLoader();
316             testNG.addListener( instantiate( cl, "org.apache.maven.surefire.testng.utils.FailFastNotifier",
317                                              Object.class ) );
318             testNG.addListener( new FailFastListener( createStoppable( reportManager, skipAfterFailureCount ) ) );
319         }
320 
321         // FIXME: use classifier to decide if we need to pass along the source dir (only for JDK14)
322         if ( sourcePath != null )
323         {
324             testNG.setSourcePath( sourcePath );
325         }
326 
327         testNG.setOutputDirectory( reportsDirectory.getAbsolutePath() );
328     }
329 
330     private static Stoppable createStoppable( final RunListener reportManager, int skipAfterFailureCount )
331     {
332         final AtomicInteger currentFaultCount = new AtomicInteger( skipAfterFailureCount );
333 
334         return new Stoppable()
335         {
336             public void fireStopEvent()
337             {
338                 if ( countDownToZero( currentFaultCount ) )
339                 {
340                     FailFastEventsSingleton.getInstance().setSkipOnNextTest();
341                 }
342 
343                 reportManager.testExecutionSkippedByUser();
344             }
345         };
346     }
347 
348     // If we have access to IResultListener, return a ConfigurationAwareTestNGReporter
349     // But don't cause NoClassDefFoundErrors if it isn't available; just return a regular TestNGReporter instead
350     private static TestNGReporter createTestNGReporter( RunListener reportManager )
351     {
352         try
353         {
354             Class.forName( "org.testng.internal.IResultListener" );
355             Class c = Class.forName( "org.apache.maven.surefire.testng.ConfigurationAwareTestNGReporter" );
356             @SuppressWarnings( "unchecked" ) Constructor<?> ctor = c.getConstructor( RunListener.class );
357             return (TestNGReporter) ctor.newInstance( reportManager );
358         }
359         catch ( InvocationTargetException e )
360         {
361             throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e.getCause() );
362         }
363         catch ( ClassNotFoundException e )
364         {
365             return new TestNGReporter( reportManager );
366         }
367         catch ( Exception e )
368         {
369             throw new RuntimeException( "Bug in ConfigurationAwareTestNGReporter", e );
370         }
371     }
372 
373     private static int extractVerboseLevel( Map<String, String> options )
374         throws TestSetFailedException
375     {
376         try
377         {
378             String verbose = options.get( "surefire.testng.verbose" );
379             return verbose == null ? 0 : Integer.parseInt( verbose );
380         }
381         catch ( NumberFormatException e )
382         {
383             throw new TestSetFailedException( "Provider property 'surefire.testng.verbose' should refer to "
384                                                   + "number -1 (debug mode), 0, 1 .. 10 (most detailed).", e );
385         }
386     }
387 }